<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <!-- <link rel="stylesheet" href="css/css.css"> -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js"></script>
    <title>羊了个羊</title>
    <style>
        .box {
          position: relative;
        }
        .intro {
          margin: 10% auto 0 auto;
          text-align: center;
        }
        
        .card-wrap {
          position: relative;
          margin: 10% auto 0 auto;
        }
        
        .card-item {
          font-size: 28px;
          text-align: center;
          position: absolute;
          border-radius: 2px;
          box-sizing: border-box;
          background: #ddd;
          opacity: 1;
          cursor: pointer;
          transition: all 0.3s;
          box-shadow: 0px 3px 0 0 #fff, 0 8px 0 0 #ddd, 0 8px 0 2px #333, 0 0 0 2px #333;
        }
        
        .card-item:hover {
          transform: scale3d(1.1, 1.1, 1.1);
          z-index: 1;
        }
        
        .item-cover {
          pointer-events: none;
          box-shadow: 0px 3px 0 0 #999, 0 8px 0 0 #666, 0 8px 0 2px #000, 0 0 0 2px #000;
        }
        
        .item-cover:after {
          border-radius: 2px;
          content: "";
          position: absolute;
          width: 100%;
          height: 100%;
          left: 0;
          top: 0;
          background: #000;
          opacity: 0.55;
        }
        
        .card-tips {
          white-space: nowrap;
          position: absolute;
          left: 50%;
          top: 130%;
          transform: translate(-50%, 0);
          pointer-events: none;
        }
        
        .tools {
          position: absolute;
          top: 200%;
          width: 100%;
          left: 0;
          text-align: center;
        }
        .clear-item {
          pointer-events: none;
        }
        </style>
</head>

<body>
<div id="app">
        <div v-if="step === 0" class="intro">
            <div>
                横向卡片最大平铺排数
                <input
                    v-model="option.x" min="2"
                    max="10" type="range"
                > {{ option.x }}
            </div>
            <div>
                纵向卡片最大平铺排数
                <input
                    v-model="option.y" min="2"
                    max="10" type="range"
                > {{ option.y }}
            </div>
            <div>
                卡片最大堆叠层数
                <input
                    v-model="option.z" min="2"
                    max="10" type="range"
                > {{ option.z }}
            </div>
            <div>
                卡片密度
                <input
                    v-model="option.cardRandom" min="0"
                    max="1" step="0.1"
                    type="range"
                >
                {{ option.cardRandom }}
            </div>
            <div>
                最大卡片种类
                <input
                    v-model="option.maxCardType" min="3"
                    max="12" step="1"
                    type="range"
                >
                {{ option.maxCardType }}
            </div>
            <br>
            <button @click="startGame">开始游戏</button>
        </div>
        <div v-else-if="step === 2" class="intro">
            <h1>{{ result ? "You Win！🎉" : "You Lose!😢" }}</h1>
            <button @click="rePlay">再来一轮</button>
            <button @click="setGame">难度调节</button>
        </div>
        <div v-else class="box">
            <div class="card-wrap" :style="cardWrapStyle">
                <div
                    v-for="item in cardItemList"
                    :key="item.key"
                    :class="{'item-cover': item.cover}"
                    class="card-item"
                    :style="item.style"
                    @click="clickCard(item)"
                >
                    {{ item.content }}
                </div>
                <div
                    v-for="item in penddingList"
                    :key="item.key"
                    class="card-item"
                    :style="item.style"
                >
                    {{ item.content }}
                </div>
                <div
                    v-for="item in clearList"
                    :key="item.key"
                    class="card-item clear-item"
                    :style="item.style"
                >
                    {{ item.content }}
                </div>
                <div
                    v-for="item in saveList"
                    :key="item.key"
                    class="card-item"
                    :style="item.style"
                    @click="clickSaveCard(item)"
                >
                    {{ item.content }}
                </div>
                <p class="card-tips">
                    剩余空位:{{ 7 - penddingList.length }}/7；已消除:{{ clearList.length }}/{{
                        cardItemList.length + penddingList.length + saveList.length + clearList.length
                    }}
                </p>
            </div>
            <div class="tools">
                道具：
                <button :disabled="!tools.save" @click="saveCard">取出3个卡片</button>
                <button :disabled="!tools.rand" @click="randCard">随机</button>
                <button @click="rePlay">再来一轮</button>
            </div>
        </div>
    
    <script>
    
    class CardItem {
        static x = 20;
        static y = 21;
        static colorType = {
            1: {background: '#FFB7DD'},
            2: {background: '#FFCCCC'},
            3: {background: '#FFC8B4'},
            4: {background: '#FFDDAA'},
            5: {background: '#FFEE99'},
            6: {background: '#FFFFBB'},
            7: {background: '#EEFFBB'},
            8: {background: '#CCFF99'},
            9: {background: '#99FF99'},
            10: {background: '#BBFFEE'},
            11: {background: '#AAFFEE'},
            12: {background: '#99FFFF'},
            // 13: {background: '#CCEEFF'},
            // 14: {background: '#CCDDFF'}
        };
        static contentType = {
            1: '♒',
            2: '♈',
            3: '♉',
            4: '♓',
            5: '♋',
            6: '♌',
            7: '♍',
            8: '♎',
            9: '♏',
            10: '♐',
            11: '♑',
            12: '♊',
        };
        constructor({x, y, z, key}) {
            this.x = x;
            this.y = y;
            this.z = z;
            this.key = key;
            const offset = z * 0;
            this.val = key;
            this.style = {
                top: y * CardItem.y + offset + 'px',
                left: x * CardItem.x + offset + 'px',
                width: CardItem.x * 2 - 2 + 'px',
                height: CardItem.y * 2 - 8 + 'px'
            };
        }
    
        setValue(val) {
            this.val = val;
            this.content = CardItem.contentType[val];
            Object.assign(this.style, CardItem.colorType[val]);
        }
    }
    new Vue({
        el:'#app',
        data() {
            return {
                option: {
                    x: 6,
                    y: 4,
                    z: 8,
                    cardRandom: 0.2,
                    maxCardType: 11
                },
                step: 0,
                win: false,
                cardMap: [],
                cardItemList: [],
                penddingList: [],
                clearList: [],
                saveList: [],
                calcValueList: [],
                maxWidth: 0,
                maxHeight: 0,
                tools: {
                    save: true,
                    rand: true
                },
                timer: 0
            };
        },
        computed: {
            cardWrapStyle() {
                return {
                    width: (this.maxWidth + 2) * CardItem.x + 'px',
                    height: (this.maxHeight + 1) * CardItem.y + 'px'
                };
            },
            leftOffset() {
                const wrapWidth = (this.maxWidth + 2) * CardItem.x;
                return (wrapWidth - 7 * CardItem.x * 2) / 2;
            }
        },
        methods: {
            randCard() {
                if (!this.tools.rand) {
                    return;
                }
                this.tools.rand = false;
                const length = this.cardItemList.length;
                this.cardItemList.forEach(item => {
                    const randNum = Math.floor(length * Math.random());
                    const newItem = this.cardItemList[randNum];
                    let temp;
                    temp = item.style.left;
                    item.style.left = newItem.style.left;
                    newItem.style.left = temp;
                    temp = item.style.top;
                    item.style.top = newItem.style.top;
                    newItem.style.top = temp;
                    temp = item.x;
                    item.x = newItem.x;
                    newItem.x = temp;
                    temp = item.y;
                    item.y = newItem.y;
                    newItem.y = temp;
                    temp = item.z;
                    item.z = newItem.z;
                    newItem.z = temp;
                });
    
                this.cardItemList.sort((a, b) => a.z - b.z);
                this.calcCover();
            },
            saveCard() {
                if (!this.tools.save) {
                    return false;
                }
                this.tools.save = false;
                this.saveList = this.penddingList.slice(0, 3);
                setTimeout(() => {
                    this.saveList.forEach((item, index) => {
                        item.style.top = '110%';
                        item.style.left = this.leftOffset + index * CardItem.x * 2 + 'px';
                        this.calcValueList[item.val]--;
                    });
                }, 0);
                this.penddingList = this.penddingList.slice(3);
                this.penddingList.forEach((item, index) => {
                    item.style.top = '160%';
                    item.style.left = this.leftOffset + index * CardItem.x * 2 + 'px';
                });
            },
            initGame() {
                this.step = 1;
                this.getMap(this.option);
                this.penddingList = [];
                this.clearList = [];
                this.saveList = [];
                this.tools.save = true;
                this.tools.rand = true;
                this.setCardValue({maxCardType: Number(this.option.maxCardType)});
                this.calcCover();
            },
            // 表示地图最大为 x * y 张牌，最多有 z 层
            getMap({x, y, z, cardRandom} = {}) {
                this.maxWidth = (x - 1) * 2;
                this.maxHeight = (y - 1) * 2 + 1;
                const cardMap = new Array(z);
                const cardItemList = [];
                let key = 0;
                // 地图初始化
                for (let k = 0; k < z; k++) {
                    cardMap[k] = new Array(this.maxHeight);
                    for (let i = 0; i <= this.maxHeight; i++) {
                        cardMap[k][i] = new Array(this.maxWidth).fill(0);
                    }
                }
                for (let k = 0; k < z; k++) {
                    const shrink = Math.floor((z - k) / 3);
                    // 行
                    for (let i = shrink; i < this.maxHeight - shrink; i++) {
                        // 列，对称设置
                        const mid = Math.ceil((this.maxWidth - shrink) / 2);
                        for (let j = shrink; j <= mid; j++) {
                            let canSetCard = true;
                            if (j > 0 && cardMap[k][i][j - 1]) {
                                // 左边不能有牌
                                canSetCard = false;
                            }
                            else if (i > 0 && cardMap[k][i - 1][j]) {
                                // 上边不能有牌
                                canSetCard = false;
                            }
                            else if (i > 0 && j > 0 && cardMap[k][i - 1][j - 1]) {
                                // 左上不能有牌
                                canSetCard = false;
                            }
                            else if (i > 0 && cardMap[k][i - 1][j + 1]) {
                                // 右上不能有牌
                                canSetCard = false;
                            }
                            else if (k > 0 && cardMap[k - 1][i][j]) {
                                // 正底不能有牌
                                canSetCard = false;
                            }
                            else if (Math.random() >= cardRandom) {
                                canSetCard = false;
                            }
                            if (canSetCard) {
                                key++;
                                const cardItem = new CardItem({x: j, y: i, z: k, key});
                                cardMap[k][i][j] = cardItem;
                                cardItemList.push(cardItem);
                                // 对称放置
                                if (j < mid) {
                                    key++;
                                    const cardItem = new CardItem({
                                        x: this.maxWidth - j,
                                        y: i,
                                        z: k,
                                        key
                                    });
                                    cardMap[k][i][j] = cardItem;
                                    cardItemList.push(cardItem);
                                }
                            }
                        }
                    }
                }
                cardItemList.reverse();
                for (let i = 1; i <= key % 3; i++) {
                    const clearItem = cardItemList.pop();
                    cardMap[clearItem.z][clearItem.y][clearItem.x] = 0;
                }
                cardItemList.reverse();
                this.cardMap = cardMap;
                this.cardItemList = cardItemList;
            },
            setCardValue({maxCardType} = {}) {
                // 卡片种类
                const valStack = new Array(maxCardType);
                this.calcValueList = new Array(maxCardType + 1).fill(0);
                // 给卡片设置值
                this.cardItemList.forEach(item => {
                    const value = Math.ceil(Math.random() * maxCardType);
                    if (valStack[value]) {
                        valStack[value].push(item);
                        if (valStack[value].length === 3) {
                            valStack[value].forEach(item => {
                                item.setValue(value);
                            });
                            valStack[value] = null;
                        }
                    }
                    else {
                        valStack[value] = [item];
                    }
                });
    
                let count = 2;
                // console.log(valStack)
                valStack.forEach(list => {
                    list
                        && list.forEach(item => {
                            count++;
                            item.setValue(Math.floor(count / 3));
                        });
                });
            },
            // 计算遮挡关系
            calcCover() {
                // 构建一个遮挡 map
                const coverMap = new Array(this.maxHeight);
                for (let i = 0; i <= this.maxHeight; i++) {
                    coverMap[i] = new Array(this.maxWidth).fill(false);
                }
    
                // 从后往前，后面的层数高
                for (let i = this.cardItemList.length - 1; i >= 0; i--) {
                    const item = this.cardItemList[i];
                    const {x, y, z} = item;
                    if (coverMap[y][x]) {
                        item.cover = true;
                    }
                    else if (coverMap[y][x + 1]) {
                        item.cover = true;
                    }
                    else if (coverMap[y + 1][x]) {
                        item.cover = true;
                    }
                    else if (coverMap[y + 1][x + 1]) {
                        item.cover = true;
                    }
                    else {
                        item.cover = false;
                    }
                    coverMap[y][x] = true;
                    coverMap[y + 1][x] = true;
                    coverMap[y][x + 1] = true;
                    coverMap[y + 1][x + 1] = true;
                }
            },
            clickSaveCard(item) {
                this.cardItemList.push(item);
                const index = this.saveList.indexOf(item);
                this.saveList = this.saveList
                    .slice(0, index)
                    .concat(this.saveList.slice(index + 1));
                this.clickCard(item);
            },
            removeThree() {
                this.penddingList.some(item => {
                    if (this.calcValueList[item.val] === 3) {
                        this.penddingList.forEach(newItem => {
                            if (newItem.val === item.val) {
                                this.clearList.push(newItem);
                            }
                        });
                        setTimeout(() => {
                            this.clearList.forEach((item, index) => {
                                item.style.left = this.leftOffset - 60 + 'px';
                            });
                        }, 300);
    
                        this.penddingList = this.penddingList.filter(newItem => {
                            return newItem.val !== item.val;
                        });
                        this.penddingList.forEach((item, index) => {
                            item.style.top = '160%';
                            item.style.left = this.leftOffset + index * CardItem.x * 2 + 'px';
                        });
                        this.calcValueList[item.val] = 0;
                        if (this.cardItemList.length === 0) {
                            this.step = 2;
                            this.result = true;
                        }
                    }
                });
    
                if (this.penddingList.length >= 7) {
                    this.step = 2;
                    this.result = false;
                }
            },
            // 点击卡片
            clickCard(item) {
                clearTimeout(this.timer);
                this.removeThree();
                this.penddingList.push(item);
                const index = this.cardItemList.indexOf(item);
                this.cardItemList = this.cardItemList
                    .slice(0, index)
                    .concat(this.cardItemList.slice(index + 1));
                this.calcCover();
                this.calcValueList[item.val]++;
                setTimeout(() => {
                    item.style.top = '160%';
                    item.style.left
                        = this.leftOffset + (this.penddingList.length - 1) * CardItem.x * 2 + 'px';
                }, 0);
    
                this.timer = setTimeout(() => {
                    this.removeThree();
                }, 500);
            },
            // 开始
            startGame() {
                this.initGame();
            },
            // 设置
            setGame() {
                this.step = 0;
            },
            // 重来
            rePlay() {
                this.initGame();
            }
        }
    })
      
    </script>
    
  
    
</div>
</body>

</html>